package controller;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import scheduler.DuplicateEventException;
import scheduler.Notifiable;
import scheduler.Scheduler;

/**
 * ControlConfiguration is a class to encapsulate all the regisered
 * user interface keyboard controls to appropriate actions within
 * a <code>Controller.</code>
 * 
 * @deviation
 * Renamed 'addControl' to 'registerControl'
 * 
 * @deviation
 * Renamed 'removeControl' to 'unregisterControl'
 * 
 * @deviation
 * Removed the keyPressed() operation and replaced functionality
 * with the execute(char) operation.
 * 
 * @deviation
 * Added an operation to register a list of controls in one
 * call. Added more as a convenience.
 * 
 * @deviation
 * Added the ability to reassign a GameControl based on
 * its id. 
 * 
 * 
 * @author matt
 *
 */
public class ControlConfiguration implements KeyListener {
	
	private List<GameControl> mControls;
	
	private Map<Integer, Boolean> mKeys;
	
	private Notifiable mKeyTimer;
	
	private long mDelay;
	
	
	/**
	 * Default constructor. Initializes the list to empty.
	 */
	public ControlConfiguration() {
		mControls = new ArrayList<GameControl>();
		mKeys = new HashMap<Integer, Boolean>();
		
		mDelay = 60;
		
		mKeyTimer = new Notifiable() {
			private long mTimeTilExecute = mDelay;
			private long mLastExecute;

			@Override
			public void execute() {
				
				synchronized (ControlConfiguration.this) {
				
					long sinceLastExecute = (System.currentTimeMillis() - mLastExecute);
					mLastExecute = System.currentTimeMillis();
					
					if (sinceLastExecute > mTimeTilExecute) {
						mTimeTilExecute = mDelay;
						// execute the keys
						for (Integer i : mKeys.keySet()) {
							if (mKeys.get(i)) {
								ControlConfiguration.this.execute(i.intValue());
							}
						}
					}
					else {
						mTimeTilExecute -= sinceLastExecute;
					}
					
				}
				
				
			}
		
		};
		
		// Schedule the listener
		try {
			Scheduler.getPersistantClock().schedule(mKeyTimer, 1);
		} catch (DuplicateEventException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	/**
	 * Executes the GameContorl objects that are bound
	 * to the specified <code>keyChar</code>
	 * 
	 * @param keyCode	char	The triggering character.
	 */
	public void execute(int keyCode) {
		for (GameControl gc : mControls) {
			if (gc == null) {
				System.err.format("ControlConfiguration::execute() > ERROR: GameControl was null! (keyCode=%d)\n", keyCode);
				continue;
			}
			gc.execute(keyCode);
		}
	}
	
	/**
	 * Registers a new <code>GameControl</code> to be executed
	 * when the <code>execute()</code> operation is invoked with
	 * a valid keyCode.
	 * 
	 * @param control	GameControl		The GameControl to register.
	 */
	public void registerControl(GameControl control) {
		mControls.add(control);
		
		System.out.format("ControlConfiguration::registerControl() > Added control {keyCode=%d id=%s desc=%s}\n",
				control.getActionCode(),
				control.getId(),
				control.getDescription());
	}
	
	/**
	 * Registers a list of <code>GameControl</code> to be executed
	 * when the <code>execute()</code> operation is invoked with a
	 * valid keyCode.
	 * 
	 * @param controlList
	 */
	public void registerControlList(List<GameControl> controlList) {
		for (GameControl ctrl : controlList) {
			registerControl(ctrl);
		}
	}
	
	/**
	 * Unregisters a <code>GameControl</code> that was previously 
	 * registered so that is no longer executed when the
	 * <code>execute()</code> operation is invoked with a valid
	 * keyCode.
	 * 
	 * @param control	GameControl		The GameControl to unregiser.
	 */
	public void unregisterControl(GameControl control) {
		mControls.remove(control);
	}


	@Override
	public void keyPressed(KeyEvent e) {
		synchronized (this) {
			int keyCode = e.getKeyCode();
			
			mKeys.put(new Integer(keyCode), new Boolean(true));
		}
	}


	@Override
	public void keyReleased(KeyEvent e) {
		synchronized (this) {
			int keyCode = e.getKeyCode();
		
			
			for (GameControl ctrl : mControls) {
				if (ctrl.getActionCode() == keyCode) {
					ctrl.keyReleased();
				}
			}
			mKeys.put(new Integer(keyCode), new Boolean(false));
		}
	}
	
	/**
	 * Reassigns a GameControl to a new keyCode based on its
	 * identifier.
	 * 
	 * @param actionId		String		The ID of the GameControl
	 * @param newKeyCode	int			The new key code to bind to
	 */
	public void reassign(String actionId, int newKeyCode) {
		for (GameControl gc : mControls) {
			
			// If the control cannot be reassigned, go to the next one
			if (! gc.isReassignable()) continue;
			
			if (gc.getId().equalsIgnoreCase(actionId)) {

				
				System.out.format("ControlConfiguration::reassign() > Control Reassigned {id=%s key=%d->%d desc=%s}\n",
									gc.getId(),
									gc.getActionCode(),
									newKeyCode,
									gc.getDescription());
				
				// reassign the key
				gc.setActionCode(newKeyCode);
			}
		}
	}
	
	public List<GameControl> getAllControls() {
		return (mControls);
	}


	@Override
	public void keyTyped(KeyEvent e) {}
	
	
	public String listControls() {
		StringBuilder builder = new StringBuilder();
		
		for (GameControl ctrl : mControls) {
			builder.append(ctrl.toString());
			builder.append("\n");
		}
		return (builder.toString());
	}


	public List<GameControl> getAllControlsMappedTo(int keyCode) {
		List<GameControl> mappedCtrls = new ArrayList<GameControl>();
		
		for (GameControl ctrl : mControls) {
			if (ctrl.getActionCode() == keyCode) {
				mappedCtrls.add(ctrl);
			}
		}
		
		return (mappedCtrls);
	}

	
	
	

}
